home *** CD-ROM | disk | FTP | other *** search
/ Revista do CD-ROM 151 / cd-rom 151.iso / internet / firefox / Firefox Setup 3.0 Beta 1.exe / nonlocalized / components / storage-Legacy.js < prev    next >
Encoding:
Text File  |  2007-11-09  |  30.2 KB  |  931 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is mozilla.org code.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla Corporation.
  17.  * Portions created by the Initial Developer are Copyright (C) 2007
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Justin Dolske <dolske@mozilla.com> (original author)
  22.  *
  23.  * Alternatively, the contents of this file may be used under the terms of
  24.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  25.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  26.  * in which case the provisions of the GPL or the LGPL are applicable instead
  27.  * of those above. If you wish to allow use of your version of this file only
  28.  * under the terms of either the GPL or the LGPL, and not to allow others to
  29.  * use your version of this file under the terms of the MPL, indicate your
  30.  * decision by deleting the provisions above and replace them with the notice
  31.  * and other provisions required by the GPL or the LGPL. If you do not delete
  32.  * the provisions above, a recipient may use your version of this file under
  33.  * the terms of any one of the MPL, the GPL or the LGPL.
  34.  *
  35.  * ***** END LICENSE BLOCK ***** */
  36.  
  37.  
  38. const Cc = Components.classes;
  39. const Ci = Components.interfaces;
  40.  
  41. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  42.  
  43. function LoginManagerStorage_legacy() { };
  44.  
  45. LoginManagerStorage_legacy.prototype = {
  46.  
  47.     classDescription  : "LoginManagerStorage_legacy",
  48.     contractID : "@mozilla.org/login-manager/storage/legacy;1",
  49.     classID : Components.ID("{e09e4ca6-276b-4bb4-8b71-0635a3a2a007}"),
  50.     QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManagerStorage]),
  51.  
  52.     __logService : null, // Console logging service, used for debugging.
  53.     get _logService() {
  54.         if (!this.__logService)
  55.             this.__logService = Cc["@mozilla.org/consoleservice;1"].
  56.                                 getService(Ci.nsIConsoleService);
  57.         return this.__logService;
  58.     },
  59.  
  60.     __decoderRing : null,  // nsSecretDecoderRing service
  61.     get _decoderRing() {
  62.         if (!this.__decoderRing)
  63.             this.__decoderRing = Cc["@mozilla.org/security/sdr;1"].
  64.                                  getService(Ci.nsISecretDecoderRing);
  65.         return this.__decoderRing;
  66.     },
  67.  
  68.     _prefBranch : null,  // Preferences service
  69.  
  70.     _signonsFile : null,  // nsIFile for "signons2.txt" (or whatever pref is)
  71.     _debug       : false, // mirrors signon.debug
  72.  
  73.  
  74.     /*
  75.      * Core datastructures
  76.      *
  77.      * EG: _logins["http://site.com"][0].password
  78.      * EG: _disabledHosts["never.site.com"]
  79.      */
  80.     _logins        : null, 
  81.     _disabledHosts : null,
  82.  
  83.  
  84.     /*
  85.      * log
  86.      *
  87.      * Internal function for logging debug messages to the Error Console.
  88.      */
  89.     log : function (message) {
  90.         if (!this._debug)
  91.             return;
  92.         dump("PwMgr Storage: " + message + "\n");
  93.         this._logService.logStringMessage("PwMgr Storage: " + message);
  94.     },
  95.  
  96.  
  97.  
  98.  
  99.     /* ==================== Public Methods ==================== */
  100.  
  101.  
  102.  
  103.  
  104.     initWithFile : function(aInputFile, aOutputFile) {
  105.         this._signonsFile = aInputFile;
  106.  
  107.         this.init();
  108.  
  109.         if (aOutputFile) {
  110.             this._signonsFile = aOutputFile;
  111.             this._writeFile();
  112.         }
  113.     },
  114.  
  115.     /*
  116.      * init
  117.      *
  118.      * Initialize this storage component and load stored passwords from disk.
  119.      */
  120.     init : function () {
  121.         this._logins  = {};
  122.         this._disabledHosts = {};
  123.  
  124.         // Connect to the correct preferences branch.
  125.         this._prefBranch = Cc["@mozilla.org/preferences-service;1"]
  126.                                 .getService(Ci.nsIPrefService);
  127.         this._prefBranch = this._prefBranch.getBranch("signon.");
  128.         this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
  129.  
  130.         this._debug = this._prefBranch.getBoolPref("debug");
  131.  
  132.         // Check to see if the internal PKCS#11 token has been initialized.
  133.         // If not, set a blank password.
  134.         var tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"]
  135.                             .getService(Ci.nsIPK11TokenDB);
  136.  
  137.         var token = tokenDB.getInternalKeyToken();
  138.         if (token.needsUserInit) {
  139.             this.log("Initializing key3.db with default blank password.");
  140.             token.initPassword("");
  141.         }
  142.  
  143.         var importFile = null;
  144.         // If initWithFile is calling us, _signonsFile is already set.
  145.         if (!this._signonsFile)
  146.             [this._signonsFile, importFile] = this._getSignonsFile();
  147.  
  148.         // If we have an import file, do a switcharoo before reading it.
  149.         if (importFile) {
  150.             this.log("Importing " + importFile.path);
  151.  
  152.             var tmp = this._signonsFile;
  153.             this._signonsFile = importFile;
  154.         }
  155.  
  156.         // Read in the stored login data.
  157.         this._readFile();
  158.  
  159.         // If we were importing, write back to the normal file.
  160.         if (importFile) {
  161.             this._signonsFile = tmp;
  162.             this._writeFile();
  163.         }
  164.     },
  165.  
  166.  
  167.     /*
  168.      * addLogin
  169.      *
  170.      */
  171.     addLogin : function (login) {
  172.         // We rely on using login.wrappedJSObject. addLogin is the
  173.         // only entry point where we might get a nsLoginInfo object
  174.         // that wasn't created by us (and so might not be a JS
  175.         // implementation being wrapped)
  176.         if (!login.wrappedJSObject) {
  177.             var clone = Cc["@mozilla.org/login-manager/loginInfo;1"].
  178.                         createInstance(Ci.nsILoginInfo);
  179.             clone.init(login.hostname, login.formSubmitURL, login.httpRealm,
  180.                        login.username,      login.password,
  181.                        login.usernameField, login.passwordField);
  182.             login = clone;
  183.         }
  184.  
  185.         var key = login.hostname;
  186.  
  187.         // If first entry for key, create an Array to hold it's logins.
  188.         var rollback;
  189.         if (!this._logins[key]) {
  190.             this._logins[key] = [];
  191.             rollback = null;
  192.         } else {
  193.             rollback = this._logins[key].concat(); // clone array
  194.         }
  195.  
  196.         this._logins[key].push(login);
  197.  
  198.         var ok = this._writeFile();
  199.  
  200.         // If we failed, don't keep the added login in memory.
  201.         if (!ok) {
  202.             if (rollback)
  203.                 this._logins[key] = rollback;
  204.             else
  205.                 delete this._logins[key];
  206.  
  207.             throw "Couldn't write to file, login not added.";
  208.         }
  209.     },
  210.  
  211.  
  212.     /*
  213.      * removeLogin
  214.      *
  215.      */
  216.     removeLogin : function (login) {
  217.         var key = login.hostname;
  218.         var logins = this._logins[key];
  219.  
  220.         if (!logins)
  221.             throw "No logins found for hostname (" + key + ")";
  222.  
  223.         var rollback = this._logins[key].concat(); // clone array
  224.  
  225.         // The specified login isn't encrypted, so we need to ensure
  226.         // the logins we're comparing with are decrypted. We decrypt one entry
  227.         // at a time, lest _decryptLogins return fewer entries and screw up
  228.         // indices between the two.
  229.         for (var i = 0; i < logins.length; i++) {
  230.  
  231.             var [[decryptedLogin], userCanceled] =
  232.                         this._decryptLogins([logins[i]]);
  233.  
  234.             if (userCanceled)
  235.                 throw "User canceled master password entry, login not removed.";
  236.  
  237.             if (!decryptedLogin)
  238.                 continue;
  239.  
  240.             if (decryptedLogin.equals(login)) {
  241.                 logins.splice(i, 1); // delete that login from array.
  242.                 break;
  243.                 // Note that if there are duplicate entries, they'll
  244.                 // have to be deleted one-by-one.
  245.             }
  246.         }
  247.  
  248.         // Did we delete the last login for this host?
  249.         if (logins.length == 0)
  250.             delete this._logins[key];
  251.  
  252.         var ok = this._writeFile();
  253.  
  254.         // If we failed, don't actually remove the login.
  255.         if (!ok) {
  256.             this._logins[key] = rollback;
  257.             throw "Couldn't write to file, login not removed.";
  258.         }
  259.     },
  260.  
  261.  
  262.     /*
  263.      * modifyLogin
  264.      *
  265.      */
  266.     modifyLogin : function (oldLogin, newLogin) {
  267.         this.removeLogin(oldLogin);
  268.         this.addLogin(newLogin);
  269.     },
  270.  
  271.  
  272.     /*
  273.      * getAllLogins
  274.      *
  275.      * Returns an array of nsAccountInfo.
  276.      */
  277.     getAllLogins : function (count) {
  278.         var result = [], userCanceled;
  279.  
  280.         // Each entry is an array -- append the array entries to |result|.
  281.         for each (var hostLogins in this._logins) {
  282.             result = result.concat(hostLogins);
  283.         }
  284.  
  285.         // decrypt entries for caller.
  286.         [result, userCanceled] = this._decryptLogins(result);
  287.  
  288.         count.value = result.length; // needed for XPCOM
  289.         return result;
  290.     },
  291.  
  292.  
  293.     /*
  294.      * removeAllLogins
  295.      *
  296.      * Removes all logins from storage.
  297.      */
  298.     removeAllLogins : function () {
  299.         this._logins = {};
  300.         // Disabled hosts kept, as one presumably doesn't want to erase those.
  301.  
  302.         this._writeFile();
  303.     },
  304.  
  305.  
  306.     /*
  307.      * getAllDisabledHosts
  308.      *
  309.      */
  310.     getAllDisabledHosts : function (count) {
  311.         var result = [];
  312.  
  313.         for (var hostname in this._disabledHosts) {
  314.             result.push(hostname);
  315.         }
  316.  
  317.         count.value = result.length; // needed for XPCOM
  318.         return result;
  319.     },
  320.  
  321.  
  322.     /*
  323.      * getLoginSavingEnabled
  324.      *
  325.      */
  326.     getLoginSavingEnabled : function (hostname) {
  327.         return !this._disabledHosts[hostname];
  328.     },
  329.  
  330.  
  331.     /*
  332.      * setLoginSavingEnabled
  333.      *
  334.      */
  335.     setLoginSavingEnabled : function (hostname, enabled) {
  336.         if (enabled)
  337.             delete this._disabledHosts[hostname];
  338.         else
  339.             this._disabledHosts[hostname] = true;
  340.  
  341.         this._writeFile();
  342.     },
  343.  
  344.  
  345.     /*
  346.      * findLogins
  347.      *
  348.      */
  349.     findLogins : function (count, hostname, formSubmitURL, httpRealm) {
  350.         var userCanceled;
  351.  
  352.         var logins = this._searchLogins(hostname, formSubmitURL, httpRealm);
  353.  
  354.         // Decrypt entries found for the caller.
  355.         [logins, userCanceled] = this._decryptLogins(logins);
  356.  
  357.         // We want to throw in this case, so that the Login Manager
  358.         // knows to stop processing forms on the page so the user isn't
  359.         // prompted multiple times.
  360.         if (userCanceled)
  361.             throw "User canceled Master Password entry";
  362.  
  363.         count.value = logins.length; // needed for XPCOM
  364.         return logins;
  365.     },
  366.  
  367.     
  368.     /*
  369.      * countLogins
  370.      *
  371.      */
  372.     countLogins : function (hostname, formSubmitURL, httpRealm) {
  373.         var logins = this._searchLogins(hostname, formSubmitURL, httpRealm);
  374.  
  375.         return logins.length;
  376.     },
  377.  
  378.  
  379.  
  380.  
  381.     /* ==================== Internal Methods ==================== */
  382.  
  383.  
  384.  
  385.  
  386.     /*
  387.      * _searchLogins
  388.      *
  389.      */
  390.     _searchLogins : function (hostname, formSubmitURL, httpRealm) {
  391.         var hostLogins = this._logins[hostname];
  392.         if (hostLogins == null)
  393.             return [];
  394.  
  395.         var result = [], userCanceled;
  396.  
  397.         for each (var login in hostLogins) {
  398.  
  399.             // If search arg is null, skip login unless it doesn't specify a
  400.             // httpRealm (ie, it's also null). If the search arg is an empty
  401.             // string, always match.
  402.             if (httpRealm == null) {
  403.                 if (login.httpRealm != null)
  404.                     continue;
  405.             } else if (httpRealm != "") {
  406.                 // Make sure the realms match. If search arg is null,
  407.                 // only match if login doesn't specify a realm (is null)
  408.                 if (httpRealm != login.httpRealm)
  409.                     continue;
  410.             }
  411.  
  412.             // If search arg is null, skip login unless it doesn't specify a
  413.             // action URL (ie, it's also null). If the search arg is an empty
  414.             // string, always match.
  415.             if (formSubmitURL == null) {
  416.                 if (login.formSubmitURL != null)
  417.                     continue;
  418.             } else if (formSubmitURL != "") {
  419.                 // If the stored login is blank (not null), that means the
  420.                 // login was stored before we started keeping the action
  421.                 // URL, so always match. Unless the search g
  422.                 if (login.formSubmitURL != "" &&
  423.                     formSubmitURL != login.formSubmitURL)
  424.                     continue;
  425.             }
  426.  
  427.             result.push(login);
  428.         }
  429.  
  430.         return result;
  431.     },
  432.  
  433.  
  434.     /*
  435.      * _getSignonsFile
  436.      *
  437.      * Determines what file to use based on prefs. Returns it as a
  438.      * nsILocalFile, along with a file to import from first (if needed)
  439.      *
  440.      */
  441.     _getSignonsFile : function() {
  442.         var importFile = null;
  443.  
  444.         // Get the location of the user's profile.
  445.         var DIR_SERVICE = new Components.Constructor(
  446.                 "@mozilla.org/file/directory_service;1", "nsIProperties");
  447.         var pathname = (new DIR_SERVICE()).get("ProfD", Ci.nsIFile).path;
  448.  
  449.  
  450.         // First try the default pref...
  451.         var filename = this._prefBranch.getCharPref("SignonFileName2");
  452.  
  453.         var file = Cc["@mozilla.org/file/local;1"].
  454.                    createInstance(Ci.nsILocalFile);
  455.         file.initWithPath(pathname);
  456.         file.append(filename);
  457.  
  458.         if (!file.exists()) {
  459.             this.log("SignonFilename2 file does not exist. file=" +
  460.                      filename + ", path=" + pathname);
  461.  
  462.             // Then try the old pref...
  463.             var oldname = this._prefBranch.getCharPref("SignonFileName");
  464.  
  465.             importFile = Cc["@mozilla.org/file/local;1"].
  466.                          createInstance(Ci.nsILocalFile);
  467.             importFile.initWithPath(pathname);
  468.             importFile.append(oldname);
  469.  
  470.             if (!importFile.exists()) {
  471.                 this.log("SignonFilename1 file does not exist. file=" +
  472.                         oldname + ", path=" + pathname);
  473.                 importFile = null;
  474.             }
  475.         }
  476.  
  477.         return [file, importFile];
  478.     },
  479.  
  480.  
  481.     /*
  482.      * _readFile
  483.      *
  484.      */
  485.     _readFile : function () {
  486.         var oldFormat = false;
  487.  
  488.         this.log("Reading passwords from " + this._signonsFile.path);
  489.  
  490.         // If it doesn't exist, just create an empty file and bail out.
  491.         if (!this._signonsFile.exists()) {
  492.             this.log("Creating new signons file...");
  493.             this._writeFile();
  494.             return;
  495.         }
  496.  
  497.         var inputStream = Cc["@mozilla.org/network/file-input-stream;1"]
  498.                                 .createInstance(Ci.nsIFileInputStream);
  499.         // init the stream as RD_ONLY, -1 == default permissions.
  500.         inputStream.init(this._signonsFile, 0x01, -1, null);
  501.         var lineStream = inputStream.QueryInterface(Ci.nsILineInputStream);
  502.         var line = { value: "" };
  503.  
  504.         const STATE = { HEADER : 0, REJECT : 1, REALM : 2,
  505.                         USERFIELD : 3, USERVALUE : 4,
  506.                         PASSFIELD : 5, PASSVALUE : 6, ACTIONURL : 7 };
  507.         var parseState = STATE.HEADER;
  508.  
  509.         var nsLoginInfo = new Components.Constructor(
  510.                 "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo);
  511.         var processEntry = false;
  512.  
  513.         do {
  514.             var hasMore = lineStream.readLine(line);
  515.  
  516.             switch (parseState) {
  517.                 // Check file header
  518.                 case STATE.HEADER:
  519.                     if (line.value == "#2c") {
  520.                         oldFormat = true;
  521.                     } else if (line.value != "#2d") {
  522.                         this.log("invalid file header (" + line.value + ")");
  523.                         throw "invalid file header in signons file";
  524.                         // We could disable later writing to file, so we
  525.                         // don't clobber whatever it is. ...however, that
  526.                         // would mean corrupt files are not self-healing.
  527.                         return;
  528.                     }
  529.                     parseState++;
  530.                     break;
  531.  
  532.                 // Line is a hostname for which passwords should never be saved.
  533.                 case STATE.REJECT:
  534.                     if (line.value == ".") {
  535.                         parseState++;
  536.                         break;
  537.                     }
  538.  
  539.                     this._disabledHosts[line.value] = true;
  540.  
  541.                     break;
  542.  
  543.                 // Line is a hostname, saved login(s) will follow
  544.                 case STATE.REALM:
  545.                     var hostrealm = line.value;
  546.  
  547.                     // Format is "http://site.com", with "(some realm)"
  548.                     // appended if it's a HTTP-Auth login.
  549.                     const realmFormat = /^(.+?)( \(.*\))?$/; // XXX .* or .+?
  550.                     var matches = realmFormat.exec(hostrealm);
  551.  
  552.                     var hostname, httpRealm;
  553.                     if (matches && matches.length == 3) {
  554.                         hostname  = matches[1];
  555.                         httpRealm = matches[2] ?
  556.                                         matches[2].slice(2, -1) : null;
  557.                     } else {
  558.                         if (hostrealm != "") {
  559.                             // Uhoh. This shouldn't happen, but try to deal.
  560.                             this.log("Error parsing host/realm: " + hostrealm);
  561.                         }
  562.                         hostname = hostrealm;
  563.                         httpRealm = null;
  564.                     }
  565.  
  566.                     parseState++;
  567.                     break;
  568.  
  569.                 // Line is the HTML 'name' attribute for the username field
  570.                 // (or "." to indicate end of hostrealm)
  571.                 case STATE.USERFIELD:
  572.                     if (line.value == ".") {
  573.                         parseState = STATE.REALM;
  574.                         break;
  575.                     }
  576.  
  577.                     var entry = new nsLoginInfo();
  578.                     entry.hostname  = hostname;
  579.                     entry.httpRealm = httpRealm;
  580.  
  581.                     entry.usernameField = line.value;
  582.                     parseState++;
  583.                     break;
  584.  
  585.                 // Line is a username
  586.                 case STATE.USERVALUE:
  587.                     entry.wrappedJSObject.encryptedUsername = line.value;
  588.                     parseState++;
  589.                     break;
  590.  
  591.                 // Line is the HTML 'name' attribute for the password field,
  592.                 // with a leading '*' character
  593.                 case STATE.PASSFIELD:
  594.                     entry.passwordField = line.value.substr(1);
  595.                     parseState++;
  596.                     break;
  597.  
  598.                 // Line is a password
  599.                 case STATE.PASSVALUE:
  600.                     entry.wrappedJSObject.encryptedPassword = line.value;
  601.                     if (oldFormat) {
  602.                         entry.formSubmitURL = "";
  603.                         processEntry = true;
  604.                         parseState = STATE.USERFIELD;
  605.                     } else {
  606.                         parseState++;
  607.                     }
  608.                     break;
  609.  
  610.                 // Line is the action URL
  611.                 case STATE.ACTIONURL:
  612.                     var formSubmitURL = line.value;
  613.                     if (!formSubmitURL && entry.httpRealm)
  614.                         entry.formSubmitURL = null;
  615.                     else
  616.                         entry.formSubmitURL = formSubmitURL;
  617.                     processEntry = true;
  618.                     parseState = STATE.USERFIELD;
  619.                     break;
  620.  
  621.             }
  622.  
  623.             if (processEntry) {
  624.                 if (!this._logins[hostname])
  625.                     this._logins[hostname] = [];
  626.  
  627.                 this._logins[hostname].push(entry);
  628.  
  629.                 entry = null;
  630.                 processEntry = false;
  631.             }
  632.         } while (hasMore);
  633.  
  634.         lineStream.close();
  635.  
  636.         return;
  637.     },
  638.  
  639.  
  640.     /*
  641.      * _writeFile
  642.      *
  643.      * Returns true if the operation was successfully completed, or false
  644.      * if there was an error (probably the user refusing to enter a
  645.      * master password if prompted).
  646.      */
  647.     _writeFile : function () {
  648.         function writeLine(data) {
  649.             data += "\r\n";
  650.             outputStream.write(data, data.length);
  651.         }
  652.  
  653.         this.log("Writing passwords to " + this._signonsFile.path);
  654.  
  655.         var outputStream = Cc["@mozilla.org/network/safe-file-output-stream;1"]
  656.                                 .createInstance(Ci.nsIFileOutputStream);
  657.         outputStream.QueryInterface(Ci.nsISafeOutputStream);
  658.  
  659.         // WR_ONLY|CREAT|TRUNC
  660.         outputStream.init(this._signonsFile, 0x02 | 0x08 | 0x20, 0600, null);
  661.  
  662.         // write file version header
  663.         writeLine("#2d");
  664.  
  665.         // write disabled logins list
  666.         for (var hostname in this._disabledHosts) {
  667.             writeLine(hostname);
  668.         }
  669.  
  670.         // write end-of-reject-list marker
  671.         writeLine(".");
  672.  
  673.         for (var hostname in this._logins) {
  674.             function sortByRealm(a,b) {
  675.                 a = a.httpRealm;
  676.                 b = b.httpRealm;
  677.  
  678.                 if (!a && !b)
  679.                     return  0;
  680.  
  681.                 if (!a || a < b)
  682.                     return -1;
  683.  
  684.                 if (!b || b > a)
  685.                     return  1;
  686.  
  687.                 return 0; // a==b, neither is null
  688.             }
  689.  
  690.             // Sort logins by httpRealm. This allows us to group multiple
  691.             // logins for the same realm together.
  692.             this._logins[hostname].sort(sortByRealm);
  693.  
  694.  
  695.             // write each login known for the host
  696.             var lastRealm = null;
  697.             var firstEntry = true;
  698.             var userCanceled = false;
  699.             for each (var login in this._logins[hostname]) {
  700.  
  701.                 // If this login is for a new realm, start a new entry.
  702.                 if (login.httpRealm != lastRealm || firstEntry) {
  703.                     // end previous entry, if needed.
  704.                     if (!firstEntry)
  705.                         writeLine(".");
  706.  
  707.                     var hostrealm = login.hostname;
  708.                     if (login.httpRealm)
  709.                         hostrealm += " (" + login.httpRealm + ")";
  710.  
  711.                     writeLine(hostrealm);
  712.                 }
  713.  
  714.                 firstEntry = false;
  715.  
  716.                 // Get the encrypted value of the username. Newly added
  717.                 // logins will need the plaintext value encrypted.
  718.                 var encUsername = login.wrappedJSObject.encryptedUsername;
  719.                 if (!encUsername) {
  720.                     [encUsername, userCanceled] = this._encrypt(login.username);
  721.                     login.wrappedJSObject.encryptedUsername = encUsername;
  722.                 }
  723.  
  724.                 if (userCanceled)
  725.                     break;
  726.  
  727.                 // Get the encrypted value of the password. Newly added
  728.                 // logins will need the plaintext value encrypted.
  729.                 var encPassword = login.wrappedJSObject.encryptedPassword;
  730.                 if (!encPassword) {
  731.                     [encPassword, userCanceled] = this._encrypt(login.password);
  732.                     login.wrappedJSObject.encryptedPassword = encPassword;
  733.                 }
  734.  
  735.                 if (userCanceled)
  736.                     break;
  737.  
  738.  
  739.                 writeLine((login.usernameField ?  login.usernameField : ""));
  740.                 writeLine(encUsername);
  741.                 writeLine("*" +
  742.                     (login.passwordField ?  login.passwordField : ""));
  743.                 writeLine(encPassword);
  744.                 writeLine((login.formSubmitURL ? login.formSubmitURL : ""));
  745.  
  746.                 lastRealm = login.httpRealm;
  747.             }
  748.  
  749.             if (userCanceled) {
  750.                 this.log("User canceled Master Password, aborting write.");
  751.                 // .close will cause an abort w/o modifying original file
  752.                 outputStream.close();
  753.                 return false;
  754.             }
  755.  
  756.             // write end-of-host marker
  757.             writeLine(".");
  758.         }
  759.  
  760.         // [if there were no hosts, no end-of-host marker (".") needed]
  761.  
  762.         outputStream.finish();
  763.         return true;
  764.     },
  765.  
  766.  
  767.     /*
  768.      * _decryptLogins
  769.      *
  770.      * Decrypts username and password fields in the provided array of
  771.      * logins. This is deferred from the _readFile() code, so that
  772.      * the user is not prompted for a master password (if set) until
  773.      * the entries are actually used.
  774.      *
  775.      * The entries specified by the array will be decrypted, if possible.
  776.      * An array of successfully decrypted logins will be returned. The return
  777.      * value should be given to external callers (since still-encrypted
  778.      * entries are useless), whereas internal callers generally don't want
  779.      * to lose unencrypted entries (eg, because the user clicked Cancel
  780.      * instead of entering their master password)
  781.      */
  782.     _decryptLogins : function (logins) {
  783.         var result = [], userCanceled = false;
  784.  
  785.         for each (var login in logins) {
  786.             var username, password;
  787.  
  788.             [username, userCanceled] =
  789.                 this._decrypt(login.wrappedJSObject.encryptedUsername);
  790.  
  791.             if (userCanceled)
  792.                 break;
  793.  
  794.             [password, userCanceled] =
  795.                 this._decrypt(login.wrappedJSObject.encryptedPassword);
  796.  
  797.             // Probably can't hit this case, but for completeness...
  798.             if (userCanceled)
  799.                 break;
  800.  
  801.             // If decryption failed (corrupt entry?) skip it.
  802.             // Note that we allow password-only logins, so username con be "".
  803.             if (username == null || !password)
  804.                 continue;
  805.  
  806.             // We could set the decrypted values on a copy of the object, to
  807.             // try to prevent the decrypted values from sitting around in
  808.             // memory if they're not needed. But thanks to GC that's happening
  809.             // anyway, so meh.
  810.             login.username = username;
  811.             login.password = password;
  812.  
  813.             // Old mime64-obscured entries need to be reencrypted in the new
  814.             // format.
  815.             if (login.wrappedJSObject.encryptedUsername &&
  816.                 login.wrappedJSObject.encryptedUsername.charAt(0) == '~') {
  817.                   [username, userCanceled] = this._encrypt(login.username);
  818.  
  819.                   if (userCanceled)
  820.                     break;
  821.  
  822.                   login.wrappedJSObject.encryptedUsername = username;
  823.             }
  824.  
  825.             if (login.wrappedJSObject.encryptedPassword &&
  826.                 login.wrappedJSObject.encryptedPassword.charAt(0) == '~') {
  827.  
  828.                   [password, userCanceled] = this._encrypt(login.password);
  829.  
  830.                   if (userCanceled)
  831.                     break;
  832.  
  833.                   login.wrappedJSObject.encryptedPassword = password;
  834.             }
  835.  
  836.             result.push(login);
  837.         }
  838.  
  839.         return [result, userCanceled];
  840.     },
  841.  
  842.  
  843.     /*
  844.      * _encrypt
  845.      *
  846.      * Encrypts the specified string, using the SecretDecoderRing.
  847.      *
  848.      * Returns [cipherText, userCanceled] where:
  849.      *  cipherText   -- the encrypted string, or null if it failed.
  850.      *  userCanceled -- if the encryption failed, this is true if the
  851.      *                  user selected Cancel when prompted to enter their
  852.      *                  Master Password. The caller should bail out, and not
  853.      *                  not request that more things be encrypted (which 
  854.      *                  results in prompting the user for a Master Password
  855.      *                  over and over.)
  856.      */
  857.     _encrypt : function (plainText) {
  858.         var cipherText = null, userCanceled = false;
  859.  
  860.         try {
  861.             var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
  862.                             createInstance(Ci.nsIScriptableUnicodeConverter);
  863.             converter.charset = "UTF-8";
  864.             var plainOctet = converter.ConvertFromUnicode(plainText);
  865.             plainOctet += converter.Finish();
  866.             cipherText = this._decoderRing.encryptString(plainOctet);
  867.         } catch (e) {
  868.             this.log("Failed to encrypt string. (" + e.name + ")");
  869.             // If the user clicks Cancel, we get NS_ERROR_FAILURE.
  870.             // (unlike decrypting, which gets NS_ERROR_NOT_AVAILABLE).
  871.             if (e.result == Components.results.NS_ERROR_FAILURE)
  872.                 userCanceled = true;
  873.         }
  874.  
  875.         return [cipherText, userCanceled];
  876.     },
  877.  
  878.  
  879.     /*
  880.      * _decrypt
  881.      *
  882.      * Decrypts the specified string, using the SecretDecoderRing.
  883.      *
  884.      * Returns [plainText, userCanceled] where:
  885.      *  plainText    -- the decrypted string, or null if it failed.
  886.      *  userCanceled -- if the decryption failed, this is true if the
  887.      *                  user selected Cancel when prompted to enter their
  888.      *                  Master Password. The caller should bail out, and not
  889.      *                  not request that more things be decrypted (which 
  890.      *                  results in prompting the user for a Master Password
  891.      *                  over and over.)
  892.      */
  893.     _decrypt : function (cipherText) {
  894.         var plainText = null, userCanceled = false;
  895.  
  896.         try {
  897.             var plainOctet;
  898.             if (cipherText.charAt(0) == '~') {
  899.                 // The older file format obscured entries by
  900.                 // base64-encoding them. These entries are signaled by a
  901.                 // leading '~' character. 
  902.                 plainOctet = atob(cipherText.substring(1));
  903.             } else {
  904.                 plainOctet = this._decoderRing.decryptString(cipherText);
  905.             }
  906.             var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
  907.                               .createInstance(Ci.nsIScriptableUnicodeConverter);
  908.             converter.charset = "UTF-8";
  909.             plainText = converter.ConvertToUnicode(plainOctet);
  910.         } catch (e) {
  911.             this.log("Failed to decrypt string: " + cipherText +
  912.                 " (" + e.name + ")");
  913.  
  914.             // If the user clicks Cancel, we get NS_ERROR_NOT_AVAILABLE.
  915.             // If the cipherText is bad / wrong key, we get NS_ERROR_FAILURE
  916.             // Wrong passwords are handled by the decoderRing reprompting;
  917.             // we get no notification.
  918.             if (e.result == Components.results.NS_ERROR_NOT_AVAILABLE)
  919.                 userCanceled = true;
  920.         }
  921.  
  922.         return [plainText, userCanceled];
  923.     },
  924.  
  925. }; // end of nsLoginManagerStorage_legacy implementation
  926.  
  927. var component = [LoginManagerStorage_legacy];
  928. function NSGetModule(compMgr, fileSpec) {
  929.     return XPCOMUtils.generateModule(component);
  930. }
  931.